package de.bazinga.rotw.weapon;

import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

import com.jme.bounding.BoundingBox;
import com.jme.intersection.PickData;
import com.jme.intersection.TrianglePickResults;
import com.jme.math.Ray;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jmex.game.state.GameStateManager;
import com.jmex.jbullet.nodes.PhysicsNode;

import de.bazinga.rotw.base.attacker.AttackerList;
import de.bazinga.rotw.base.attacker.BaseAttacker;
import de.bazinga.rotw.collision.CollidableObjects;
import de.bazinga.rotw.effects.BulletHoleMessage;
import de.bazinga.rotw.effects.BulletHoles;
import de.bazinga.rotw.game.RotwGameState;
import de.bazinga.rotw.network.BasicSyncManager;
import de.bazinga.rotw.network.Client;
import de.bazinga.rotw.network.Server;
import de.bazinga.rotw.network.message.HitMessage;
import de.bazinga.rotw.network.message.physics.AppliedForceMessage;
import de.bazinga.rotw.person.OpponentList;
import de.bazinga.rotw.person.Person;
import de.bazinga.rotw.player.Player;
import de.bazinga.rotw.vehicle.Vehicle;


public abstract class Weapon {
	
	/**
	 * e.g. check for Mouseclick...
	 */
	public abstract void updateWeapon(Player player);
	
	
	/**
	 * Fire a bullet for the given person
	 * Can be called from PlayerActions or ServerMessageListener
	 * @param person
	 * @param location
	 * @param direction
	 * @param fromClient
	 */
	public abstract void fireBullet(Person person, Vector3f location, Vector3f direction, boolean fromClient);

	
	
	/**
	 * The Player actually fired
	 * Play some sound e.g.
	 */
	abstract public void playerFired();

	
	/**
	 * Becomes called if the player hits ammo-box
	 */
	public abstract void refillAmmo();
	
	
	/**
	 * Becomes called if the player wants to reload
	 * @return true if he can reload (has magazines)
	 */
	public abstract boolean reload();
	
	
	public boolean canReload() {
		return false;
	}
	
	
	/**
	 * The time he need to return
	 * @return in ms
	 */
	public int getReloadLag() {
		return 2500;
	}
	
	/**
	 * We need this to display some information
	 * @return
	 */
	public abstract int getAmmo();
	
	
	
	public int getMagazines() {
		return 0;
	}
	
	
	/**
	 * the icon for the weapon we display on the HUD
	 * @return
	 */
	public abstract String getIcon();
	
	
	/**
	 * The model of the weapon
	 * @return the node containing the weapon
	 */
	abstract public Node getWeaponModel();
	
	
	/**
	 * How fast can we run with the weapon
	 * @return 1f => normal speed
	 */
	abstract public float getSpeedFactor();
	
	
	
	
	/**
	 * Fires a bullet of the given Weapon with the Damage
	 * @param person the person who fired the bullet
	 * @param location the location from where he fired
	 * @param direction the direction the person fired
	 * @param weaponFlag the flag of the Weapon from NewBulletMessage
	 * @param maxDistance the maximum distance we can hit an object (negative for infinity)
	 * @param personTopDamage the damage it does on the top-third of a person
	 * @param personMiddleDamage the damage it does on the middle-third of a person
	 * @param personBottomDamage the damage it does on the bottom-third of a person
	 * @param attackerTopDamage the damage it does on the top-third of an attacker
	 * @param attackerMiddleDamage the damage it does on the middle-third of an attacker
	 * @param attackerBottomDamage the damage it does on the bottom-third of an attacker
	 * @param vehicleDamage the damage the weapon does on a vehicle
	 * @param attachBulletHole should we put a bullethole on a wall
	 */
	public static void fireBullet(Person person, Vector3f location, Vector3f direction, int weaponFlag, float maxDistance,
			int personTopDamage, int personMiddleDamage, int personBottomDamage,
			int attackerTopDamage, int attackerMiddleDamage, int attackerBottomDamage,
			int vehicleDamage, boolean attachBulletHole, boolean applyPhysics) {
		
		// Get New Location
		Ray ray = new Ray(location, direction);
		TrianglePickResults results = new TrianglePickResults();
		results.setCheckDistance(true);
		
		
		Vector<Spatial> checkSpatials = createCheckSpatials(person);
		
			
		float distance = -5;
		int indexFound = 0;
		PickData pData = null;
		for(int i=0;i<checkSpatials.size();i++) {
			checkSpatials.get(i).findPick(ray,results);
			//check if we hit something
			if(results.getNumber() > 0) {
			   PickData closest = results.getPickData(0);
			   
			   if(distance < 0 || distance > closest.getDistance()) {
				   distance = closest.getDistance();
				   indexFound = i;
				   if(CollidableObjects.getInstance().getObjects("wall").contains(checkSpatials.get(i))) 
					   pData = closest;
				   else 
					   pData = null;
				}	
			}
		}
		
		// Too far away?
		boolean tooFarAway = false;
		if((maxDistance > 0 && distance > maxDistance)) 
			tooFarAway = true;
		
		if(distance > 0 && indexFound >= 0 && !tooFarAway) {
			if(checkSpatials.get(indexFound) instanceof Person) {
				hitPerson(
						person,
						(Person) checkSpatials.get(indexFound),
						location, direction, distance, weaponFlag,
						personTopDamage, personMiddleDamage, personBottomDamage);
			}else if(checkSpatials.get(indexFound) instanceof Vehicle) {
				// we hit a vehicle
				((Vehicle)checkSpatials.get(indexFound)).setDamage(
						((Vehicle)checkSpatials.get(indexFound)).getDamage()+vehicleDamage, true);
			}else if(checkSpatials.get(indexFound) instanceof BaseAttacker) {
				hitAttacker(
						person, (BaseAttacker)checkSpatials.get(indexFound),
						location, direction, distance,
						attackerTopDamage, attackerMiddleDamage, attackerBottomDamage);
			}else if(pData != null && attachBulletHole) {
				attachBullethole(pData, location, direction, distance);
			}
			
			// PHYSICS-TEST
			if(applyPhysics && checkSpatials.get(indexFound) instanceof PhysicsNode) {
				float phyDistance = ((PhysicsNode)checkSpatials.get(indexFound)).getLocalTranslation().distance(location);
				Vector3f force = ((PhysicsNode)checkSpatials.get(indexFound)).getLocalTranslation().subtract(location).normalize().divide(phyDistance).mult(personTopDamage*13);
				((PhysicsNode)checkSpatials.get(indexFound))
					.applyImpulse(force, Vector3f.ZERO);
				
				int index = CollidableObjects.getInstance().getObjects("physics").indexOf(
						 ((PhysicsNode)checkSpatials.get(indexFound)));
				
				
				// Send message
				AppliedForceMessage afm = new AppliedForceMessage();
				afm.setPhysicsIndex(index);
				afm.setForce(force);
				
				BasicSyncManager syncManager = ((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
				if(syncManager instanceof Server) {
					((Server)syncManager).getServer().sendToAll(afm);
				}else{
					((Client)syncManager).getClient().sendToServer(afm);
				}
			}
			
		}
	}
	
	
	/**
	 * We hit a Person
	 * @param person The person that fired
	 * @param p the person that got hit
	 * @param location the location from where he fired
	 * @param direction the direction the person fired
	 * @param distance distance the person is away
	 * @param weaponFlag the flag of the Weapon from NewBulletMessage
	 * @param personTopDamage the damage it does on the top-third of a person
	 * @param personMiddleDamage the damage it does on the middle-third of a person
	 * @param personBottomDamage the damage it does on the bottom-third of a person
	 */
	private static void hitPerson(Person person, Person p, Vector3f location, Vector3f direction,
			float distance, int weaponFlag,
			int personTopDamage, int personMiddleDamage, int personBottomDamage) {
		// we hit a Person
		if(!((RotwGameState)GameStateManager.getInstance().getChild("gameState")).useTeams() || person.getTeam() != p.getTeam()) {
			Vector3f hitLoc = location.add(
								direction.mult(
										distance));
			float hitHeight = hitLoc.getY() - p.getLocalTranslation().getY();
			
			int damage = 0;
			
			/*if(hitHeight > ((2f/3f) * (2*((BoundingBox)p.getWorldBound()).yExtent))) {
				damage = personTopDamage;
			}else if(hitHeight > (1f/3f) * (2*((BoundingBox)p.getWorldBound()).yExtent)) {
				damage = personMiddleDamage;
			}else{
				damage = personBottomDamage;
			}*/
			if(hitHeight > 1) {
				damage = personTopDamage;
			}else if(hitHeight > 0) {
				damage = personMiddleDamage;
			}else{
				damage = personBottomDamage;
			}
			
			damage *= person.getDamageFactor();
			
			if(person.isDoubleDamage()) {
				damage *= 2;
			}
			
			p.hit(person, damage);
			
			// Send HitMessage
			BasicSyncManager syncManager = ((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
			
			HitMessage hitMessage = new HitMessage();
			hitMessage.setPlayerName(person.getPlayerName());
			hitMessage.setPlayerLocation(person.getCameraPosition());
			hitMessage.setDamage(damage);
			hitMessage.setWeaponFlag(weaponFlag);
			((Server) syncManager).getServer().sendToPlayer(hitMessage, p.getPlayerId());
		}
	}
	
	
	/**
	 * We hit a Attacker
	 * @param person the person that fired
	 * @param att the attacker who got hit
	 * @param location the location from where he fired
	 * @param direction the direction the person fired
	 * @param distance distance the person is away
	 * @param attackerTopDamage the damage it does on the top-third of an attacker
	 * @param attackerMiddleDamage the damage it does on the middle-third of an attacker
	 * @param attackerBottomDamage the damage it does on the bottom-third of an attacker
	 */
	private static void hitAttacker(Person person, BaseAttacker att, 
			Vector3f location, Vector3f direction, float distance,
			int attackerTopDamage, int attackerMiddleDamage, int attackerBottomDamage) {
		// We hit a baseattacker
		
		Vector3f hitLoc = location.add(
				direction.mult(
						distance));
		
		float hitHeight = hitLoc.getY() - att.getLocalTranslation().getY();
		
		int damage = 0;
		
		if(hitHeight > ((2f/3f) * (2*((BoundingBox)att.getWorldBound()).yExtent))) {
			damage = attackerTopDamage;
		}else if(hitHeight > (1f/3f) * (2*((BoundingBox)att.getWorldBound()).yExtent)) {
			damage = attackerMiddleDamage;
		}else{
			damage = attackerBottomDamage;
		}
		
		damage *= person.getDamageFactor();
		
		if(person.isDoubleDamage()) 
			damage *= 2;
		
		att.hit(person, damage * 2);
	}

	
	/**
	 * Attach a bullethole
	 * @param pData 
	 * @param location the location from where he fired
	 * @param direction the direction the person fired
	 * @param distance distance the person is away
	 */
	private static void attachBullethole(PickData pData, Vector3f location, Vector3f direction, float distance) {
		// Attach BulletHoles
		List<Integer> tris = pData.getTargetTris();
        TriMesh mesh = (TriMesh) pData.getTargetMesh();
        if(tris.size() > 0) {
        	int triIndex = tris.get(0);
            Vector3f[] vec = new Vector3f[3];
            mesh.getTriangle(triIndex, vec);
            if(vec.length == 3) {
            	Triangle pick = new Triangle(vec[0], vec[1], vec[2]);
            	Vector3f hitLoc1 = location.add(
            			direction.mult(
            					distance-0.01f));
            	BulletHoles.getInstance().setBulletHole(hitLoc1,pick);
            	
            	// Send bullethole-Message
            	if(((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer()) {
            		Server server = (Server)((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
            		
            		BulletHoleMessage bhm = new BulletHoleMessage();
            		bhm.setHitTriangle(pick);
            		bhm.setLocation(hitLoc1);
            		server.getServer().sendToAll(bhm);
            	}
            }
        }
	}
	
	
	/**
	 * Create the collision-Spatial stuff
	 * @param person the person who fired
	 * @return
	 */
	private static Vector<Spatial> createCheckSpatials(Person person) {
		Vector<Spatial> checkSpatials = new Vector<Spatial>();
		
		/**
		 * Changed this to add each single wall so we can attach bulletholes
		 */
		//checkSpatials.add(CollidableObjects.getInstance().getObjects("wallNode").get(0));
		Vector<Spatial> walls = CollidableObjects.getInstance().getObjects("wall");
		if(walls != null) {
			for(Spatial vec:walls) {
				checkSpatials.add(vec);
			}
		}
		
		Vector<Spatial> obj = CollidableObjects.getInstance().getObjects("physics");
		if(obj != null) {
			for(Spatial vec:obj) {
				checkSpatials.add(vec);
			}
		}
		
		checkSpatials.add(CollidableObjects.getInstance().getObjects("groundNode").get(0));
	
		Vector<Spatial> vehicles = CollidableObjects.getInstance().getObjects("vehicle");
		if(vehicles != null) {
			for(Spatial vec:vehicles) {
				if(!person.hasVehicle() || person.getVehicle() != (Vehicle)vec) {
					checkSpatials.add(vec);
				}
			}
		}
		
		Enumeration<Person> e = OpponentList.getInstance().getOpponents().elements();
		while(e.hasMoreElements()) {
			Person p = e.nextElement();
			if(p != person) {
				checkSpatials.add(p);
			}
		}
		
		if(AttackerList.getAttackers() != null) {
			for(BaseAttacker att:AttackerList.getAttackers()) {
				checkSpatials.add(att);
			}
		}
		
		return checkSpatials;
	}
}